// ============================================================================
//
// LCP-net framework by Pierre CHATEL chatelp@gmail.com
// Distributed under GPLv3
// Copyright (C) THALES Land & Joint Systems, 2008, 2009. All rights reserved
//
// ============================================================================

/**
 * <copyright>
 * </copyright>
 *
 * $Id$
 */
package com.thalesgroup.preferenceFramework.models.LCPnet.impl;

import java.io.File;
import java.util.Collection;
import java.util.HashMap;

import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.NotificationChain;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.impl.ENotificationImpl;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EObjectContainmentEList;
import org.eclipse.emf.ecore.util.InternalEList;
import org.eclipse.emf.ecore.xmi.impl.XMLResourceFactoryImpl;

import com.thalesgroup.graphFramework.core.Graph;
import com.thalesgroup.graphFramework.core.GraphBuilder;
import com.thalesgroup.graphFramework.core.Vertex;
import com.thalesgroup.graphFramework.core.impl.GraphBuilderImpl;
import com.thalesgroup.graphFramework.searchMethods.DepthFirstSearch;
import com.thalesgroup.preferenceFramework.models.Arc;
import com.thalesgroup.preferenceFramework.models.ModelsPackage;
import com.thalesgroup.preferenceFramework.models.Node;
import com.thalesgroup.preferenceFramework.models.NodeValue;
import com.thalesgroup.preferenceFramework.models.Outcome;
import com.thalesgroup.preferenceFramework.models.LCPnet.CIArc;
import com.thalesgroup.preferenceFramework.models.LCPnet.CNodeValue;
import com.thalesgroup.preferenceFramework.models.LCPnet.COutcome;
import com.thalesgroup.preferenceFramework.models.LCPnet.LCPnet;
import com.thalesgroup.preferenceFramework.models.LCPnet.LCPnetPackage;
import com.thalesgroup.preferenceFramework.models.LCPnet.LNode;
import com.thalesgroup.preferenceFramework.models.LCPnet.LTwoTuple;
import com.thalesgroup.preferenceFramework.models.LCPnet.LinguisticDomain;
import com.thalesgroup.preferenceFramework.models.LCPnet.exceptions.LCPnetEvaluationException;
import com.thalesgroup.preferenceFramework.models.LCPnet.util.LCPGraphSerializer;
import com.thalesgroup.preferenceFramework.models.impl.PreferenceNetworkImpl;

/**
 * <!-- begin-user-doc --> An implementation of the model object '
 * <em><b>LC Pnet</b></em>'. <!-- end-user-doc -->
 * <p>
 * The following features are implemented:
 * <ul>
 *   <li>{@link com.thalesgroup.preferenceFramework.models.LCPnet.impl.LCPnetImpl#getUtilityDomain <em>Utility Domain</em>}</li>
 *   <li>{@link com.thalesgroup.preferenceFramework.models.LCPnet.impl.LCPnetImpl#getValueDomains <em>Value Domains</em>}</li>
 * </ul>
 * </p>
 *
 * @generated
 */
public class LCPnetImpl extends PreferenceNetworkImpl implements LCPnet {
	/**
	 * The cached value of the '{@link #getUtilityDomain() <em>Utility Domain</em>}' containment reference.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @see #getUtilityDomain()
	 * @generated
	 * @ordered
	 */
	protected LinguisticDomain utilityDomain;

	/**
	 * The cached value of the '{@link #getValueDomains() <em>Value Domains</em>}' containment reference list.
	 * <!-- begin-user-doc
	 * --> <!-- end-user-doc -->
	 * @see #getValueDomains()
	 * @generated
	 * @ordered
	 */
	protected EList<LinguisticDomain> valueDomains;

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	protected LCPnetImpl() {
		super();
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	protected EClass eStaticClass() {
		return LCPnetPackage.Literals.LC_PNET;
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	public LinguisticDomain getUtilityDomain() {
		return utilityDomain;
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	public NotificationChain basicSetUtilityDomain(
			LinguisticDomain newUtilityDomain, NotificationChain msgs) {
		LinguisticDomain oldUtilityDomain = utilityDomain;
		utilityDomain = newUtilityDomain;
		if (eNotificationRequired()) {
			ENotificationImpl notification = new ENotificationImpl(this, Notification.SET, LCPnetPackage.LC_PNET__UTILITY_DOMAIN, oldUtilityDomain, newUtilityDomain);
			if (msgs == null) msgs = notification; else msgs.add(notification);
		}
		return msgs;
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	public void setUtilityDomain(LinguisticDomain newUtilityDomain) {
		if (newUtilityDomain != utilityDomain) {
			NotificationChain msgs = null;
			if (utilityDomain != null)
				msgs = ((InternalEObject)utilityDomain).eInverseRemove(this, EOPPOSITE_FEATURE_BASE - LCPnetPackage.LC_PNET__UTILITY_DOMAIN, null, msgs);
			if (newUtilityDomain != null)
				msgs = ((InternalEObject)newUtilityDomain).eInverseAdd(this, EOPPOSITE_FEATURE_BASE - LCPnetPackage.LC_PNET__UTILITY_DOMAIN, null, msgs);
			msgs = basicSetUtilityDomain(newUtilityDomain, msgs);
			if (msgs != null) msgs.dispatch();
		}
		else if (eNotificationRequired())
			eNotify(new ENotificationImpl(this, Notification.SET, LCPnetPackage.LC_PNET__UTILITY_DOMAIN, newUtilityDomain, newUtilityDomain));
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	public EList<LinguisticDomain> getValueDomains() {
		if (valueDomains == null) {
			valueDomains = new EObjectContainmentEList<LinguisticDomain>(LinguisticDomain.class, this, LCPnetPackage.LC_PNET__VALUE_DOMAINS);
		}
		return valueDomains;
	}

	public boolean isAcyclic() throws LCPnetEvaluationException {
		Graph<LNode, Void>[] finalizedGraphs = this.getAllPossibleFinalizedGraphs();
		for (Graph<LNode, Void> graph : finalizedGraphs) {
			if(DepthFirstSearch.search(graph)) {
				return false;
			}
		}
		
		return true;
	}

	/**
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated NOT
	 */
	public double getUtility(EList<String> nodeNames, EList<Double> nodeValues) throws LCPnetEvaluationException {
		if(nodeNames.size() != nodeValues.size()) {
			throw new LCPnetEvaluationException("getUtility: same amount of node names and values required !");
		}
		
		//1 - create a specific volatile crisp outcome for evaluation
		COutcome newOutcome = new COutcomeImpl();
		newOutcome.setName("volatile crisp outcome");
		newOutcome.setPreferenceNetwork(this); //<-- to destroy (opposite = preferenceNetwork.outcomes)
		EList<NodeValue> newOutcome_NodeValues = newOutcome.getNodeValues();
		
		//create corresponding node values
		for (int i=0; i<nodeNames.size(); i++) {
			String nodeName = nodeNames.get(i);
			Double nodeValue = nodeValues.get(i);
			
			//look for existing node in the model
			Node existingNode = this.getNode(nodeName);
			if(existingNode == null) {
				throw new LCPnetEvaluationException("getUtility: no node named \"" + nodeName + "\" in preference model");
			}
			
			//create volatile crisp node value
			CNodeValue cnodeValue = new CNodeValueImpl();
			cnodeValue.setName("volatile crisp node value #"+i);
			cnodeValue.setNode(existingNode); //<-- to destroy (opposite = node.domain)
			cnodeValue.setCrispValue(nodeValue);
			
			//assign node value to outcome
			if(!newOutcome_NodeValues.add(cnodeValue)) {
				throw new LCPnetEvaluationException("getUtility: can't assign crisp node value \""+ cnodeValue.getName() +"\" to volatile outcome");
			}
		}
		
		//2 - evaluate outcome
		double newOutcome_utility = newOutcome.getUtility();
		
		//3 - destroy volatile links in preference model
		for (NodeValue nodeValue : newOutcome_NodeValues) {
			nodeValue.setNode(null);
		}
		newOutcome.setPreferenceNetwork(null);
	
		//4 - return outcome evaluation value
		return newOutcome_utility;
	}

	/**
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	public LTwoTuple getLinguisticUtility(EList<String> nodeNames, EList<LTwoTuple> nodeValues) throws LCPnetEvaluationException {
		// TODO: implement this method
		// Ensure that you remove @generated or mark it @generated NOT
		throw new UnsupportedOperationException();
	}
	
	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public NotificationChain eInverseRemove(InternalEObject otherEnd,
			int featureID, NotificationChain msgs) {
		switch (featureID) {
			case LCPnetPackage.LC_PNET__UTILITY_DOMAIN:
				return basicSetUtilityDomain(null, msgs);
			case LCPnetPackage.LC_PNET__VALUE_DOMAINS:
				return ((InternalEList<?>)getValueDomains()).basicRemove(otherEnd, msgs);
		}
		return super.eInverseRemove(otherEnd, featureID, msgs);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public Object eGet(int featureID, boolean resolve, boolean coreType) {
		switch (featureID) {
			case LCPnetPackage.LC_PNET__UTILITY_DOMAIN:
				return getUtilityDomain();
			case LCPnetPackage.LC_PNET__VALUE_DOMAINS:
				return getValueDomains();
		}
		return super.eGet(featureID, resolve, coreType);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void eSet(int featureID, Object newValue) {
		switch (featureID) {
			case LCPnetPackage.LC_PNET__UTILITY_DOMAIN:
				setUtilityDomain((LinguisticDomain)newValue);
				return;
			case LCPnetPackage.LC_PNET__VALUE_DOMAINS:
				getValueDomains().clear();
				getValueDomains().addAll((Collection<? extends LinguisticDomain>)newValue);
				return;
		}
		super.eSet(featureID, newValue);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public void eUnset(int featureID) {
		switch (featureID) {
			case LCPnetPackage.LC_PNET__UTILITY_DOMAIN:
				setUtilityDomain((LinguisticDomain)null);
				return;
			case LCPnetPackage.LC_PNET__VALUE_DOMAINS:
				getValueDomains().clear();
				return;
		}
		super.eUnset(featureID);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public boolean eIsSet(int featureID) {
		switch (featureID) {
			case LCPnetPackage.LC_PNET__UTILITY_DOMAIN:
				return utilityDomain != null;
			case LCPnetPackage.LC_PNET__VALUE_DOMAINS:
				return valueDomains != null && !valueDomains.isEmpty();
		}
		return super.eIsSet(featureID);
	}

	public EList<Outcome> getBestOutcomes() {
		return null;
	}

	public EList<Outcome> orderOutcomes() {
		return null;
	}

	public EList<Outcome> getOptimalOutcomes(EList<NodeValue> constraints) {
		return null;
	}

	public Graph<LNode, Void> getFinalizedGraph(COutcome outcome) throws LCPnetEvaluationException {
		Outcome coutcome = (COutcome) outcome;
		EList<NodeValue> nodeValues = coutcome.getNodeValues();
		EList<CNodeValue> cNodeValues = new BasicEList<CNodeValue>();
		for (NodeValue nodeValue : nodeValues) {
			cNodeValues.add((CNodeValue) nodeValue);
		}
		
		return this.getFinalizedGraph(cNodeValues);
	}
	
	public Graph<LNode, Void> getFinalizedGraph(EList<CNodeValue> nodeValues) throws LCPnetEvaluationException {
		GraphBuilder<LNode, Void> builder = new GraphBuilderImpl<LNode, Void>();
		builder.createNewGraph();
		
		// 1 - Create corresponding vertices
		HashMap<LNode, Vertex<LNode, Void>> vertices = new HashMap<LNode, Vertex<LNode, Void>>();
		EList<Node> prefNodes = this.getNodes();
		for (Node prefNode : prefNodes) {	
			Vertex<LNode, Void> vertex = builder.createVertex((LNode)prefNode);
			vertices.put((LNode)prefNode, vertex);
		}
		
		// 2 - Input edges into graph
		EList<Arc> prefArcs = this.getArcs();
		for (Arc prefArc : prefArcs) {
			Node startNode, endNode;
			
			// 2.a - If CI-Arc, computes arc direction, according to current nodeValues
			if(prefArc instanceof CIArc) {
				CIArc prefCIArc = (CIArc) prefArc;
				EList<LNode> direction = prefCIArc.getArcDirection(nodeValues);
				startNode = direction.get(0);
				endNode = direction.get(1);
			}
			// 2.b - If other standard Arc, use specified preference network direction
			else {
				startNode = prefArc.getStartNode();
				endNode = prefArc.getEndNode();
			}
			
			builder.createEdge(
					vertices.get(startNode), 
					vertices.get(endNode));
		}
		
		// 3 - Finalize graph
		builder.finalizeGraph();
		Graph<LNode, Void> finalizedGraph = builder.getCurrentGraph();
		finalizedGraph.setSerializer(new LCPGraphSerializer());
		
		return finalizedGraph;
	}
	
	@SuppressWarnings("unchecked")
	public Graph<LNode, Void>[] getAllPossibleFinalizedGraphs() throws LCPnetEvaluationException {
		int nbGraphs = 1;
		
		EList<Arc> prefArcs = this.getArcs();
		for (Arc prefArc : prefArcs) {
			if(prefArc instanceof CIArc) {
				try {
					nbGraphs *= 2;
				}
				catch(Exception e) {
					throw new LCPnetEvaluationException("Too many CI-Arcs to compute finalized graphs :" + e);
				}
			}
		}
	
		Graph<LNode, Void>[] graphs = (Graph<LNode, Void>[]) new Graph[nbGraphs];
		
		for(int i=0; i<nbGraphs; i++) {
			char[] binaryChoices = Integer.toBinaryString(i).toCharArray();
			graphs[i] = this.getFinalizedGraph(binaryChoices);
		}
		
		return graphs;
	}
	
	private Graph<LNode, Void> getFinalizedGraph(char[] choices) {
		GraphBuilder<LNode, Void> builder = new GraphBuilderImpl<LNode, Void>();
		builder.createNewGraph();
		
		// 1 - Create corresponding vertices
		HashMap<LNode, Vertex<LNode, Void>> vertices = new HashMap<LNode, Vertex<LNode, Void>>();
		EList<Node> prefNodes = this.getNodes();
		for (Node prefNode : prefNodes) {	
			Vertex<LNode, Void> vertex = builder.createVertex((LNode)prefNode);
			vertices.put((LNode)prefNode, vertex);
		}
		
		// 2 - Input edges into graph
		int iArcIndex = 0;
		EList<Arc> prefArcs = this.getArcs();
		for (Arc prefArc : prefArcs) {
			Node startNode, endNode;
			
			// 2.a - If CI-Arc, fix arc direction
			if(prefArc instanceof CIArc) {
				//2 possible choices for CIArc direction
				if(iArcIndex >= choices.length || choices[iArcIndex] == '0') {
					startNode = prefArc.getStartNode();
					endNode = prefArc.getEndNode();
				} else {
					startNode = prefArc.getEndNode();
					endNode = prefArc.getStartNode();
				}
				
				iArcIndex++;
			}
			// 2.b - If other standard Arc, use specified preference network direction
			else {
				startNode = prefArc.getStartNode();
				endNode = prefArc.getEndNode();
			}
			
			builder.createEdge(
					vertices.get(startNode), 
					vertices.get(endNode));
		}
		
		// 3 - Finalize graph
		builder.finalizeGraph();
		Graph<LNode, Void> finalizedGraph = builder.getCurrentGraph();
		finalizedGraph.setSerializer(new LCPGraphSerializer());
		
		return finalizedGraph;
	}
	
	/**
	 * Load a LCPNet model from a given path
	 * @param path model location
	 * @return a loaded LCPNet or null if no model at this path
	 */
	public static LCPnet load(String path) {
		// Create a resource set.
		ResourceSet resourceSet = new ResourceSetImpl();

		// Register the default resource factory -- only needed for stand-alone!
		resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
				Resource.Factory.Registry.DEFAULT_EXTENSION, new XMLResourceFactoryImpl());

		// Register the packages -- only needed for stand-alone!
		ModelsPackage modelsPackage = ModelsPackage.eINSTANCE;
		LCPnetPackage lcpnetPackage = LCPnetPackage.eINSTANCE;
		

		// Get the URI of the model file.
		URI fileURI = URI.createFileURI(new File(path).getAbsolutePath());
		Resource resource = resourceSet.getResource(fileURI, true);

		EObject obj = resource.getContents().get(0);
		if (obj instanceof LCPnet) {
			return (LCPnet) obj;
		}
		
		return null;
	}

} // LCPnetImpl
